package org.funsciencewithyourcomputer.client;

import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.Random;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DecoratedTabPanel;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class Steg implements EntryPoint {

	Label label;
	
	Label scoreLabel, countLettersLabel, scoreWordLabel, bestScoreLabel;

	ListBox modeBox;

	Grid grid;

	TextBox input;

	SuggestBox suggestName;

	MultiWordSuggestOracle suggestOracle;

	static String errorMsg = null;
	
	static Label statusLabel;
	
	Button startButton;

	Dictionary dictionary = new Dictionary();

	Words words = new Words(dictionary);

	Lifecycle life = new Lifecycle();

	Score score = new Score(life, words);

	Grid rankingTableFrench, rankingTableEnglish;
	
	HTML userWordsLabel, hiddenWordsLabel;

	int resumeTimeLeft = 0;
	
	boolean isValidWord, isHot;
	
	char lastKeyCode = 0x0;

	Timer oneSecondTimer = new Timer() {
		public void run() {
			displayScore();
			if (!score.isGameOver()) {
				String msg = "";
				setStatus(NLS.get(NLS.TIME_LEFT, new Object[] {score.getTimeLeft()}), false);
				oneSecondTimer.schedule(1000);
			}
		}
	};

	Timer newLineTimer = new Timer() {
		public void run() {
			boolean success = words.addLine(dictionary
					.createRandomLine(new int[] { 3, 4 }, score));
			if (!success) {
				gameOver();
				return;
			}
			score.increaseLevel();
			int speed = score.getSpeed();
			score.setTimeLevelEnd(System.currentTimeMillis() + speed);
			display();
			newLineTimer.schedule(speed);
		}
	};

	Timer keyUpTimer = new Timer() {
		public void run() {
			onKeyUpTimer();
		}
	};

	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		HighScore.readCookie();
		String lastPlayer = HighScore.getLastPlayer();
		score.name = lastPlayer;
		score.language = NLS.getLanguage();
		
		DecoratedTabPanel tabPanel = new DecoratedTabPanel();
		tabPanel.setAnimationEnabled(true);
		DockPanel dockPanel = new DockPanel();
		dockPanel.setStylePrimaryName("mainPanel");
		dockPanel.setSpacing(4);
		dockPanel.setHorizontalAlignment(DockPanel.ALIGN_CENTER);
		HTML html = new HTML(
				"<a href=\"http://www.funsciencewithyourcomputer.org\">www.funsciencewithyourcomputer.org</a>",
				true);
		html.setStylePrimaryName("extraInfo");
		
		HTML htmlSwitch = new HTML(NLS.get(NLS.SWITCH_LANGUAGE), true);
		htmlSwitch.setStylePrimaryName("extraInfo");

		HTML htmlSwitch2 = new HTML(NLS.get(NLS.SWITCH_LANGUAGE2), true);
		htmlSwitch2.setStylePrimaryName("extraInfo");

		modeBox = new ListBox();
		modeBox.addItem(NLS.get(NLS.MODE_ORIGINAL));
		modeBox.addItem(NLS.get(NLS.MODE_ACTION));
		modeBox.addChangeListener(new ChangeListener() {
			public void onChange(Widget sender) {
				updateMode(modeBox.getSelectedIndex() == 0 ? Lifecycle.MODE_ORIGINAL
						: Lifecycle.MODE_ACTION);
			}
		});

		VerticalPanel vPanel = new VerticalPanel();
		vPanel.setHorizontalAlignment(DockPanel.ALIGN_CENTER);
		vPanel.setSpacing(4);
		vPanel.setStylePrimaryName("playPanel");

		grid = new Grid(Words.N_Lines, Words.N_Characters);
		grid.setStyleName(STYLE_gridGame);
		for (int row = 0; row < Words.N_Lines; row++) {
			for (int col = 0; col < Words.N_Characters; col++) {
				grid.getCellFormatter().setWidth(row, col, "14px");
				grid.getCellFormatter().setHeight(row, col, "14px");
				grid.getCellFormatter().setAlignment(row, col,
						HasHorizontalAlignment.ALIGN_CENTER,
						HasVerticalAlignment.ALIGN_MIDDLE);
			}
		}

		input = new TextBox();
		input.addKeyboardListener(new KeyboardListenerAdapter() {
			@Override
			public void onKeyUp(Widget sender, char keyCode, int modifiers) {
				Steg.this.onKeyUp(keyCode);
			}

		});
		input.addFocusListener(new FocusListener() {
			public void onLostFocus(Widget sender) {
			}

			public void onFocus(Widget sender) {
				if (input.getText().equals(NLS.get(NLS.INPUT_INFO))) {
					input.setText("");
					if (life.state == Lifecycle.START)
						started();
				}
			}
		});
		input.setText(NLS.get(NLS.INPUT_INFO));
		input.setStylePrimaryName("inputWord");

		suggestOracle = new MultiWordSuggestOracle();
		suggestOracle.setDefaultSuggestionsFromText(HighScore.getNames());

		HorizontalPanel horizontalPanel = new HorizontalPanel();
		Label nameLabel = new Label();
		nameLabel.setText(NLS.get(NLS.NAME));
		nameLabel.setStylePrimaryName("labelName");
		suggestName = new SuggestBox(suggestOracle);
		suggestName.setText(lastPlayer);
		suggestName.setStylePrimaryName("suggestName");
		suggestName.setWidth("6em");
		suggestName.addValueChangeHandler(new ValueChangeHandler<String>(){
			public void onValueChange(ValueChangeEvent<String> event) {
				score.name = suggestName.getText();
				System.out.println(suggestName.getText());
				displayScore();
			}
		});
		suggestName.addSelectionHandler(new SelectionHandler<Suggestion>(){
			public void onSelection(SelectionEvent<Suggestion> event) {
				System.out.println("selection "+suggestName.getText());
				score.name = suggestName.getText();
				displayScore();
			}
		});
		horizontalPanel.add(nameLabel);
		horizontalPanel.add(suggestName);

		startButton = new Button(NLS.get(NLS.START));
		startButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				if (life.state == Lifecycle.START)
					started();
				else if (life.state == Lifecycle.GAMEOVER)
					newGame();
				else if (life.state == Lifecycle.STARTED) {
					if (life.mode == Lifecycle.MODE_ORIGINAL)
						gameOver();
					else
						paused();
				} else if (life.state == Lifecycle.PAUSED)
					resume();
			}
		});
		startButton.setStylePrimaryName("startButton");

		rankingTableFrench = new Grid(11, 7);
		rankingTableFrench.setStylePrimaryName("gridRank");
		rankingTableEnglish = new Grid(11, 7);
		rankingTableEnglish.setStylePrimaryName("gridRank");
		DecoratedTabPanel rankPanel = new DecoratedTabPanel();
		if (NLS.getLanguage().equals("fr")) {
			rankPanel.add(rankingTableFrench, NLS.get(NLS.RANK_TAB_FRENCH));
			rankPanel.add(rankingTableEnglish, NLS.get(NLS.RANK_TAB_ENGLISH));
		} else {
			rankPanel.add(rankingTableEnglish, NLS.get(NLS.RANK_TAB_ENGLISH));
			rankPanel.add(rankingTableFrench, NLS.get(NLS.RANK_TAB_FRENCH));
		}
		rankPanel.selectTab(0);
		displayRanking();

		//vPanel.add(horizontalPanel);
		vPanel.add(grid);
		vPanel.add(input);

		statusLabel = new Label();
		statusLabel.setText(" ");
		statusLabel.setStylePrimaryName("statusEmpty");
		dockPanel.add(statusLabel, DockPanel.NORTH);		
		dockPanel.add(modeBox, DockPanel.NORTH);
		
		VerticalPanel scorePanel = new VerticalPanel();
		scorePanel.setStylePrimaryName("scorePanel");
		label = new Label();
		label.setText(NLS.get(NLS.GAME_NAME));
		label.setStylePrimaryName("stegLabel");
		scorePanel.add(label);
		scorePanel.add(horizontalPanel);
		scoreLabel = new Label();
		scoreLabel.setStylePrimaryName("scoreLabel");
		scorePanel.add(scoreLabel);
		scoreWordLabel = new Label();
		scoreWordLabel.setStylePrimaryName("scoreWordLabel");
		scorePanel.add(scoreWordLabel);
		countLettersLabel = new Label();
		countLettersLabel.setStylePrimaryName("countLettersLabel");
		scorePanel.add(countLettersLabel);
		bestScoreLabel = new Label();
		bestScoreLabel.setStylePrimaryName("scoreLabel");
		scorePanel.add(bestScoreLabel);
		scorePanel.add(startButton);
		scorePanel.setCellHorizontalAlignment(startButton, HasHorizontalAlignment.ALIGN_CENTER);
				
		VerticalPanel copyrightsPanel = new VerticalPanel();
		copyrightsPanel.add(htmlSwitch);		
		userWordsLabel = new HTML();
		userWordsLabel.setStylePrimaryName("userWordsLabel");		
		copyrightsPanel.add(userWordsLabel);
		hiddenWordsLabel = new HTML();
		hiddenWordsLabel.setStylePrimaryName("hiddenWordsLabel");		
		copyrightsPanel.add(hiddenWordsLabel);		
		copyrightsPanel.add(htmlSwitch2);		
		HTML htmlCopyrigths = new HTML(NLS.get(NLS.COPYRIGHTS));
		htmlCopyrigths.setStylePrimaryName("extraInfo");
		copyrightsPanel.add(htmlCopyrigths);
		copyrightsPanel.add(html);

		HorizontalPanel centerPanel = new HorizontalPanel();
		centerPanel.add(vPanel);
		centerPanel.add(scorePanel);
		centerPanel.setSpacing(10);
		//playPanel.add(dockPanel);
		dockPanel.add(centerPanel, DockPanel.CENTER);
		dockPanel.add(copyrightsPanel, DockPanel.SOUTH);
		
		tabPanel.add(dockPanel, NLS.get(NLS.TAB_PLAY));
		tabPanel.add(rankPanel, NLS.get(NLS.TAB_RANKING));
		tabPanel.add(new HTML(NLS.get(NLS.TAB_HOWTO)), NLS.get(NLS.TAB_INFO));
		tabPanel.selectTab(0);
		RootPanel.get().add(tabPanel);

		life.state = Lifecycle.START;
		displayScore();
	}

	public void started() {
		life.state = Lifecycle.LOADING;
		modeBox.setEnabled(false);
		startButton.setText(NLS.get(NLS.LOADING));
		dictionary.read(this);
		display();
	}

	public void paused() {
		life.state = Lifecycle.PAUSED;
		oneSecondTimer.cancel();
		newLineTimer.cancel();
		score.pause();
		resumeTimeLeft = score.getTimeLeft();
		startButton.setText(NLS.get(NLS.RESUME));
	}

	public void resume() {
		life.state = Lifecycle.STARTED;
		startButton.setText(NLS.get(NLS.PAUSE));
		if (life.mode == Lifecycle.MODE_ACTION) {
			oneSecondTimer.schedule(1000);
			score.setTimeLevelEnd(System.currentTimeMillis() + resumeTimeLeft
					* 1000);
			newLineTimer.schedule(resumeTimeLeft * 1000);
		}
	}

	public void dictionaryLoaded() {
		/**
		 * In original mode, fill up the grid. In action mode, fill up the first
		 * line.
		 */
		if (life.mode == Lifecycle.MODE_ORIGINAL) {
			int index7Letters = Random.nextInt(Words.N_Lines);
			int index5Letters1 = Random.nextInt(Words.N_Lines);
			int index5Letters2 = Random.nextInt(Words.N_Lines);
			for (int i = 0; i < Words.N_Lines; i++) {
				/**
				 * Create 1 seven letters word, 2 five letters word
				 */
				int[] lettersCount;
				if (i == index7Letters)
					lettersCount = new int[] {7};
				else if (i == index5Letters1 || i == index5Letters2)
					lettersCount = new int[] {5,2};
				else
					lettersCount = new int[] {3,4};
				words.addLine(dictionary.createRandomLine(lettersCount, score));
			}
			// horizontal or vertical
			if (Random.nextBoolean())
				words.lines = Words.rotate(words.lines);
		} else {
			words.addLine(dictionary.createRandomLine(new int[] { 3, 4 }, score));			
		}
		int speed = score.getSpeed();
		score.setTimeLevelEnd(System.currentTimeMillis() + speed);

		life.state = Lifecycle.STARTED;
		startButton.setText(NLS
				.get(life.mode == Lifecycle.MODE_ORIGINAL ? NLS.STOP_GAME
						: NLS.PAUSE));
		if (life.mode == Lifecycle.MODE_ACTION) {
			newLineTimer.schedule(speed);
			oneSecondTimer.schedule(1000);
		}
		input.setFocus(true);
		display();
	}

	public void onKeyUp(char keyCode) {
		lastKeyCode = keyCode;
		keyUpTimer.cancel();
		keyUpTimer.schedule(100);
	}
	
	void onKeyUpTimer() {
		char keyCode = lastKeyCode;
		lastKeyCode = 0x0;
		if (keyCode == '\r') {
			String letters = input.getText().toUpperCase();
			int count = words.removeAndFall(letters);
			if (count != 0) {
				score.add(letters, count);
				userWordsLabel.setHTML(score.getUserWords());
			}
			input.setText("");
			input.setStylePrimaryName("inputWord");
			display();
			return;
		}
		String letters = input.getText().toUpperCase();
		isValidWord = dictionary.isWord(letters);
		display();
		input.setStylePrimaryName(isHot && isValidWord ? "inputValidWord" : "inputWord");
	}

	public void gameOver() {
		life.state = Lifecycle.GAMEOVER;
		modeBox.setEnabled(true);
		score.setGameOver();
		oneSecondTimer.cancel();
		newLineTimer.cancel();
		String name = suggestName.getText();
		score.name = name;
		score.language = NLS.getLanguage();
		HighScore.saveCookie(score);
		displayRanking();
		display();
		startButton.setText(NLS.get(NLS.START));
		hiddenWordsLabel.setHTML(score.getHiddenWords());
	}

	public void newGame() {
		life = new Lifecycle();
		life.mode = modeBox.getSelectedIndex() == 0 ? Lifecycle.MODE_ORIGINAL
				: Lifecycle.MODE_ACTION;
		modeBox.setEnabled(false);

		words = new Words(dictionary);
		score = new Score(life, words);

		// Update the suggestion box
		suggestOracle.setDefaultSuggestionsFromText(HighScore.getNames());

		userWordsLabel.setHTML(" ");
		hiddenWordsLabel.setHTML(" ");
		
		dictionaryLoaded();
	}

	public void updateMode(int newMode) {
		life.mode = newMode;
	}

	public void display() {
		isHot = displayLines();
		displayScore();
	}

	public void displayRanking() {
		HighScore.readCookie();
		int row = 1;
		rankingTableFrench.setText(0, 0, NLS.get(NLS.RANKING_RANK));
		rankingTableFrench.setText(0, 1, NLS.get(NLS.RANKING_NAME));
		rankingTableFrench.setText(0, 2, NLS.get(NLS.RANKING_COUNTSTEGS));
		rankingTableFrench.setText(0, 3, NLS.get(NLS.RANKING_BESTSCORE));
		rankingTableFrench.setText(0, 4, NLS.get(NLS.RANKING_BESTWORD));
		rankingTableFrench.setText(0, 5, NLS.get(NLS.RANKING_NUMBERPLAYS));
		rankingTableFrench.setText(0, 6, NLS.get(NLS.RANKING_CUMULATIVESCORE));
		for (HighScore score : HighScore.scores) {
			if (score.language.equals("fr")) {
				rankingTableFrench.setText(row, 0, "" + row);
				rankingTableFrench.setText(row, 1, score.name);
				rankingTableFrench.setText(row, 2, "" + score.countStegs);
				rankingTableFrench.setText(row, 3, "" + score.bestScore);
				rankingTableFrench.setText(row, 4, score.bestWord);
				rankingTableFrench.setText(row, 5, "" + score.numberPlays);
				rankingTableFrench.setText(row, 6, "" + score.cumulativeScore);
				row++;
				if (row >= rankingTableEnglish.getRowCount())
					break;
			}
		}
		row = 1;
		rankingTableEnglish.setText(0, 0, NLS.get(NLS.RANKING_RANK));
		rankingTableEnglish.setText(0, 1, NLS.get(NLS.RANKING_NAME));
		rankingTableEnglish.setText(0, 2, NLS.get(NLS.RANKING_COUNTSTEGS));
		rankingTableEnglish.setText(0, 3, NLS.get(NLS.RANKING_BESTSCORE));
		rankingTableEnglish.setText(0, 4, NLS.get(NLS.RANKING_BESTWORD));
		rankingTableEnglish.setText(0, 5, NLS.get(NLS.RANKING_NUMBERPLAYS));
		rankingTableEnglish.setText(0, 6, NLS.get(NLS.RANKING_CUMULATIVESCORE));
		for (HighScore score : HighScore.scores) {
			if (score.language.equals("en")) {
				rankingTableEnglish.setText(row, 0, "" + row);
				rankingTableEnglish.setText(row, 1, score.name);
				rankingTableEnglish.setText(row, 2, "" + score.countStegs);
				rankingTableEnglish.setText(row, 3, "" + score.bestScore);
				rankingTableEnglish.setText(row, 4, score.bestWord);
				rankingTableEnglish.setText(row, 5, "" + score.numberPlays);
				rankingTableEnglish.setText(row, 6, "" + score.cumulativeScore);
				row++;
				if (row >= rankingTableEnglish.getRowCount())
					break;
			}
		}

	}

	public void displayScore() {
		scoreLabel.setText(NLS.get(NLS.SCORE, new Object[] {score.getScore()}));
		HighScore hs = HighScore.getHighScore(score.name, score.language);
		bestScoreLabel.setText(NLS.get(NLS.BESTSCORE_LABEL, new Object[] {hs != null ? hs.bestScore : 0}));
		countLettersLabel.setText(NLS.get(NLS.COUNT_LETTERS, new Object[] {score.getCountLetters()}));
		if (score.isGameOver()) {
			setStatus(NLS.get(NLS.GAME_OVER), false);
		} else {
			setStatus(null, false);
		}
		if (isValidWord && isHot) {
			String letters = input.getText().toUpperCase();
			Words temp = new Words(words);
			int count = temp.remove(letters);
			int value = score.getScore(letters, count);
			String word = letters.toLowerCase();
			if (count == 1)
				scoreWordLabel.setText(NLS.get(NLS.SCORE_WORD, new Object[] {word, value}));
			else
				scoreWordLabel.setText(NLS.get(NLS.SCORE_WORD_MULTIPLE, new Object[] {word, count, value}));
		} else {
			scoreWordLabel.setText(NLS.get(NLS.SCORE_WORD_INVALID));
		}
	}

	public boolean displayLines() {
		String word = input.getText().toUpperCase();
		return displayLines(words, word);
	}

	boolean displayLines(Words words, String word) {
		List<int[]> hIndeces = words.highlightIndeces(word, true);
		List<int[]> vIndeces = words.highlightIndeces(word, false);
		boolean isHot = false;
		for (int row = 0; row < grid.getRowCount(); row++) {
			int index = grid.getRowCount() - 1 - row;
			String line = 0 <= index && index < words.lines.size() ? words.lines
					.get(index)
					: Words.EMPTY_LINE;
			for (int column = 0; column < grid.getColumnCount(); column++) {
				// grid.setText(row, column, "" + line.charAt(column));
				char character = line.charAt(column);
				if (character == ' ')
					character = '\u00A0';
				String text = "" + character;
				boolean hHot = words.isHot(index, column, hIndeces, true);
				boolean vHot = words.isHot(index, column, vIndeces, false);
				if (hHot || vHot)
					isHot = true;
				String style;
				if (hHot && vHot && word.length() > 1)
					style = STYLE_gridCellHotBoth;
				else if (hHot)
					style = STYLE_gridCellHotHorizontal;
				else if (vHot)
					style = STYLE_gridCellHotVertical;
				else
					style = STYLE_gridCellNormal;
				setText(grid, row, column, text, style);
			}
		}
		return isHot;
	}

	public static void setStatus(String msg, boolean error) {
		if (error) {
			if (errorMsg == null)
				errorMsg = "";
			errorMsg = msg + ". " + errorMsg;
			statusLabel.setStylePrimaryName("statusMsgError");
			statusLabel.setText(errorMsg);
			return;
		}
		if (errorMsg != null)
			return;
		if (msg == null) {
			// clear up
			statusLabel.setStylePrimaryName("statusEmpty");
			statusLabel.setText(" ");
			return;
		}
		statusLabel.setStylePrimaryName("statusMsg");
		statusLabel.setText(msg);
	}
	
	public static final String STYLE_gridGame = "gridGame";

	public static final String STYLE_gridCellNormal = "gridCellNormal";

	public static final String STYLE_gridCellHotHorizontal = "gridCellHotHorizontal";

	public static final String STYLE_gridCellHotVertical = "gridCellHotVertical";

	public static final String STYLE_gridCellHotBoth = "gridCellHotBoth";

	static void setText(Grid grid, int row, int column, String text,
			String style) {
		String html = "<div class=\"" + style + "\">" + text.toUpperCase()
				+ "</div>";
		grid.setHTML(row, column, html);
	}
}
